
/* ICMP.java */

package SSF.OS;

import java.util.*;
import com.renesys.raceway.DML.*;
import SSF.Net.*;
import SSF.Net.Util.*;
import com.renesys.raceway.SSF.*;


/**
  * This class implements a subset of the ICMP protocol. 
  *
  *  <ul>
  *  <li>Register to receive ICMP packets using <code>listen()</code>
  *  <li>See <code>SSF.OS.ICMPHeader</code> for ICMP message factory methods
  *  <li>Ping a host using <code>ping()</code>
  *  <li>Stop receiving ICMP packets using <code>close()</code>
  *  </ul>
  */
public class ICMP extends ProtocolSession implements Configurable {

    ProtocolSession ip;

    private boolean DEBUG  = false;

    // ICMP message types
    public static final int ICMP_ECHO_RESPONSE            =  0;
    public static final int ICMP_DESTINATION_UNREACHABLE  =  3;
    public static final int ICMP_SOURCE_QUENCH            =  4;
    public static final int ICMP_REDIRECT                 =  5;
    public static final int ICMP_ECHO_REQUEST             =  8;
    public static final int ICMP_TIME_EXCEEDED            = 11;
    public static final int ICMP_PARAMETER_FAULT          = 12;
    public static final int ICMP_TIMESTAMP_REQUEST        = 13;
    public static final int ICMP_TIMESTAMP_RESPONSE       = 14;

    // ICMP message codes for type ICMP_DESTINATION_UNREACHABLE
    public static final int ICMP_CODE_HOST_UNREACHABLE      =  1;
    public static final int ICMP_CODE_PROTOCOL_UNREACHABLE  =  2;
    public static final int ICMP_CODE_PORT_UNREACHABLE      =  3;

    //----------------------------------------------------------------------
    // ICMP PACKET SUPPRESSION
    
    /** Returns true if no ICMP message should be generated when 
     *  an ICMP packet is dropped.  ICMP error report packets
     *  disappear silently -- no errors about errors!
     */
    public static boolean suppressReporting(IpHeader badpkt) {
	if (Protocols.ICMP_PRTL_NUM==badpkt.PROTOCOL_NO) {
	    ICMPHeader ih = (ICMPHeader)badpkt.payload();
	    switch(ih.icmpType) {
	    case ICMP_DESTINATION_UNREACHABLE:
	    case ICMP_SOURCE_QUENCH:
	    case ICMP_REDIRECT:
	    case ICMP_TIME_EXCEEDED:
	    case ICMP_PARAMETER_FAULT:
		return true;
	    default:
		return false;
	    }
	}
	return false;
    }

    //------------------------------------------------------ CLIENTS
    // 
    //  For max simplicity, we don't implement raw ICMP sockets --
    //  instead, a protocol may register to receive copies of every 
    //  ICMP packet that arrives. 
    //

    ArrayList listeners = null;

    public void listen(ProtocolSession listener) {
	if (null==listeners) listeners = new ArrayList();
	listeners.add(listener);
    }

    public void finished(ProtocolSession listener) {
	if (null!=listeners) listeners.remove(listener);
    }

    private void dispatchToListeners(ICMPHeader icmp) throws ProtocolException{
	if (null!=listeners)
	    for (int n=0; n<listeners.size(); n++)
		((ProtocolSession)listeners.get(n)).push(icmp,this);
    }
    
    //------------------------------------------------------ PING
    
    private int nextSeq = 0;

    private void singlePing(int id, int seq, int len, int destaddr,int ttl) 
	throws ProtocolException {
	if (null!=ip) {
	    ICMPHeader echo = 
		ICMPHeader.makeEchoRequestMessage(id,seq,len,destaddr);
	    echo.ipHeader().TIME_TO_LIVE = ttl;
	    ip.push(echo.ipHeader(),this);
	}
	else throw new ProtocolException("ICMP.singlePing(): no IP protocol");
    }

    /** Send a single ping of 'len' bytes to the given destination IP 
     *  IP address.  Returns the integer id of the ping; to hear responses,
     *  first call listen(), and look for ICMP ECHO RESPONSE packets 
     *  with the matching id among those pushed to you.
     */
    public int ping(int len, int destaddr)  throws ProtocolException {
	int id = nextSeq++;
	singlePing(id,0,len,destaddr,IpHeader.DEFAULT_TIME_TO_LIVE);
	return id;
    }

    /** Send 'ct' pings of 'len' bytes to the given destination IP 
     *  IP address, one ping every 'intervalSeconds' seconds.  Returns 
     *  the integer id of the ping; to hear responses, first call listen(), 
     *  and look for ICMP ECHO RESPONSE packets with the matching id among 
     *  those pushed to you.
     */
    public int ping(int len, int destaddr, int ct, 
		    double intervalSeconds,int ttl) throws ProtocolException {
	final int f_len = len;
	final int f_destaddr = destaddr;
	final int f_id = nextSeq++;
	final int f_ttl = ttl;

	if (ct>0) singlePing(f_id,0,f_len,f_destaddr,f_ttl);
	    
	final int f_ct = ct-1;

	if (f_ct>0)
	    (new Timer(inGraph(),Net.seconds(intervalSeconds)) {
		    int pct = f_ct;
		    public void callback() {
			if (pct>0) {
			    try {
				singlePing(f_id,f_ct-pct+1,
					   f_len,f_destaddr,f_ttl);
			    } catch (ProtocolException pex) {
				pex.printStackTrace(); // STUB 
			    }
			    pct--;
			    set();
			}
		    }
		}).set();
	return f_id;
    }

    public int ping(int len, int destaddr, int ct, 
		    double intervalSeconds) throws ProtocolException {
	return ping(len,destaddr,ct,intervalSeconds,
		    IpHeader.DEFAULT_TIME_TO_LIVE);
    }
    
    //------------------------------------------------------ PUSH
    
    public boolean push(ProtocolMessage message, 
			ProtocolSession fromSession) 
	throws ProtocolException {
	ICMPHeader imsg = (ICMPHeader)message;
	
	// Take any local action based on the message,
	// and then pass a copy to all registered ICMP
	// listeners. 

	if (fromSession instanceof IP) 
	    switch(imsg.icmpType) {
	    case ICMP_ECHO_REQUEST:
		if (null!=ip){
		    ProtocolMessage echo = 
			ICMPHeader.makeEchoResponseMessage(imsg).ipHeader();
		    ip.push(echo,this);
		}
		break;
	    case ICMP_TIMESTAMP_REQUEST:
		if (null!=ip){
		    ProtocolMessage tstamp = 
			ICMPHeader.makeTimestampResponseMessage
			(imsg,inGraph().now()).ipHeader();
		    ip.push(tstamp,this);
		}
		break;
	    case ICMP_DESTINATION_UNREACHABLE:
		break;
	    case ICMP_SOURCE_QUENCH:
		break;
	    case ICMP_REDIRECT:
		break;
	    case ICMP_TIME_EXCEEDED:
		break;
	    case ICMP_PARAMETER_FAULT:
		break;
	    case ICMP_ECHO_RESPONSE:
		break;
	    case ICMP_TIMESTAMP_RESPONSE:
		break;
	    default:
		break;
	    }

	if (DEBUG) 
	    System.out.println("ICMP@"+debugIdentifier()+" received "+imsg);

	dispatchToListeners(imsg);
	return true;

    }

//------------------------------------------------------ CONFIGURATION

    public void config(Configuration cfg) throws configException {
	super.config(cfg);
	String str;
	
	str = (String)cfg.findSingle("debug");
	if (str != null)
	    DEBUG = Boolean.valueOf(str).booleanValue();

	//try {   moved to init() 12-13-01
	//    ip = inGraph().SessionForName("ip");
	//} catch (ProtocolException pex) {
	//    pex.printStackTrace(); // STUB  TO init()
	//}
    }

     public void init() throws ProtocolException {
       ip = inGraph().SessionForName("ip");
     }
}

/*=                                                                      =*/
/*=  Copyright (c) 1997--2001  SSF Research Network                      =*/
/*=                                                                      =*/
/*=  SSFNet is open source software, distributed under the GNU General   =*/
/*=  Public License.  See the file COPYING in the 'doc' subdirectory of  =*/
/*=  the SSFNet distribution, or http://www.fsf.org/copyleft/gpl.html    =*/
/*=                                                                      =*/
